package com.naiterui.ehp.bs.b2c.order.service.impl;

import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.naiterui.ehp.bp.bo.b2c.order.RefundOrderBO;
import com.naiterui.ehp.bp.bo.patient.MsgMaPublishParamBO;
import com.naiterui.ehp.bp.bo.payment.NotifyBusinessRefundBO;
import com.naiterui.ehp.bp.bo.recommend.MedicationRecomBO;
import com.naiterui.ehp.bp.support.exception.BusinessException;
import com.naiterui.ehp.bp.utils.payment.OrderUtils;
import com.naiterui.ehp.bp.utils.payment.bo.RefundRequestBO;
import com.naiterui.ehp.bs.b2c.order.entity.Order;
import com.naiterui.ehp.bs.b2c.order.entity.OrderItem;
import com.naiterui.ehp.bs.b2c.order.entity.RefundOrder;
import com.naiterui.ehp.bs.b2c.order.entity.RefundOrderLog;
import com.naiterui.ehp.bs.b2c.order.exception.OrderBusinessException;
import com.naiterui.ehp.bs.b2c.order.exception.OrderExceptionCodes;
import com.naiterui.ehp.bs.b2c.order.fegin.IPatientFeignClient;
import com.naiterui.ehp.bs.b2c.order.fegin.IRecommendFeignClient;
import com.naiterui.ehp.bs.b2c.order.repository.IOrderItemRepository;
import com.naiterui.ehp.bs.b2c.order.repository.IOrderRepository;
import com.naiterui.ehp.bs.b2c.order.repository.IRefundOrderLogRepository;
import com.naiterui.ehp.bs.b2c.order.repository.IRefundOrderRepository;
import com.naiterui.ehp.bs.b2c.order.service.IRefundOrderService;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Service
public class RefundOrderServiceImpl implements IRefundOrderService {

    @Autowired
    private IOrderRepository orderRepository;
    @Autowired
    private IRefundOrderRepository refundOrderRepository;
    @Autowired
    private IRefundOrderLogRepository refundOrderLogRepository;
    @Autowired
    private IOrderItemRepository orderItemRepository;
    @Autowired
    private IPatientFeignClient patientFeignClient;
    @Autowired
    private IRecommendFeignClient recommendFeignClient;
    @Value("${ehp.domain.b2c}")
    private String b2cHost;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void update(RefundOrderBO refundOrderBO, Integer submit, String operator) throws OrderBusinessException {
        RefundOrder refundOrder = Optional.ofNullable(refundOrderBO.getId())
                                          .map(id -> this.refundOrderRepository.get(id))
                                          .orElseGet(() -> RefundOrder.builder()
                                                                      .status(RefundOrder.STATUS_WAIT_SUBMIT)
                                                                      .refundOrderSn(OrderUtils.getBusinessSn("RF"))
                                                                      .createdAt(new Date())
                                                                      .createdBy(operator)
                                                                      .delFlag(RefundOrder.DEL_FLAG_NO)
                                                                      .build());
        if (refundOrder.getStatus() != RefundOrder.STATUS_WAIT_SUBMIT) {
            // 2019/9/30 10:58 上午 王泽浩 退款订单状态不正确，抛出异常
            throw new OrderBusinessException(OrderExceptionCodes.ORDER_STATUS_ERROR);
        }
        Long orderId = refundOrderBO.getOrderId();
        Order order = this.orderRepository.get(orderId);
        String orderSn = order.getOrderSn();
        Integer amount = refundOrderBO.getAmount();
        if (order.getRealPay() < amount) {
            log.warn("退款金额大于订单金额，orderSn:{}", orderSn);
            throw new OrderBusinessException(OrderExceptionCodes.REFUND_ORDER_AMOUNT_ERROR);
        }
        Long id = refundOrder.getId();
        boolean isCreate = id == null;
        if (!isCreate) {
            List<RefundOrder> refundOrders = this.refundOrderRepository.findByOrderIdAndStatusAndDelFlag(id, RefundOrder.STATUS_COMPLETE,
                                                                                                         RefundOrder.DEL_FLAG_NO);
            int sum = refundOrders.stream().mapToInt(RefundOrder::getAmount).sum();
            if (order.getRealPay() - sum < amount) {
                log.warn("退款金额大于剩余订单金额，orderSn:{}", orderSn);
                throw new OrderBusinessException(OrderExceptionCodes.REFUND_ORDER_AMOUNT_ERROR);
            }
        }

        refundOrder.setOrderSn(orderSn);
        refundOrder.setOrderId(orderId);
        refundOrder.setAmount(amount);
        refundOrder.setMethod(refundOrderBO.getMethod());
        refundOrder.setPaymentAccount(refundOrderBO.getPaymentAccount());
        refundOrder.setReason(refundOrderBO.getReason());
        refundOrder.setRemark(refundOrderBO.getRemark());
        refundOrder.setStatus(submit == 0 ? RefundOrder.STATUS_WAIT_SUBMIT : RefundOrder.STATUS_WAIT_AUDIT);
        refundOrder.setChangedAt(new Date());
        refundOrder.setChangedBy(operator);
        this.refundOrderRepository.save(refundOrder);
        this.saveRefundOrderLog(refundOrder.getId(), refundOrder.getRefundOrderSn(),
                                isCreate ? RefundOrderLog.OPERATE_CREATE : RefundOrderLog.OPERATE_UPDATE, "", operator);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void del(Long refundOrderId, String operator) throws OrderBusinessException {
        RefundOrder refundOrder = this.refundOrderRepository.get(refundOrderId);
        if (refundOrder == null) {
            throw new OrderBusinessException(OrderExceptionCodes.ORDER_NOT_FOUND);
        }
        refundOrder.setDelFlag(RefundOrder.DEL_FLAG_YES);
        refundOrder.setChangedAt(new Date());
        refundOrder.setChangedBy(operator);
        this.refundOrderRepository.update(refundOrder);
        this.saveRefundOrderLog(refundOrder.getId(), refundOrder.getRefundOrderSn(), RefundOrderLog.OPERATE_PAY_DELETE, "", operator);
    }

    @Override
    public void reverseAudit(Set<Long> refundOrderIds, String operator) throws OrderBusinessException {
        List<RefundOrder> refundOrders = this.refundOrderRepository.findByIdInAndDelFlag(refundOrderIds, RefundOrder.DEL_FLAG_NO);
        for (RefundOrder refundOrder : refundOrders) {
            if (refundOrder.getStatus() == RefundOrder.STATUS_COMPLETE) {
                throw new OrderBusinessException(OrderExceptionCodes.ORDER_STATUS_ERROR);
            }
        }
        refundOrders.forEach(refundOrder -> {
            refundOrder.setStatus(RefundOrder.STATUS_WAIT_SUBMIT);
            refundOrder.setAuditTime(null);
            refundOrder.setChangedAt(new Date());
            refundOrder.setChangedBy(operator);
            this.refundOrderRepository.update(refundOrder);
            this.saveRefundOrderLog(refundOrder.getId(), refundOrder.getRefundOrderSn(), RefundOrderLog.OPERATE_REVERSE_AUDIT, "", operator);
        });
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void submit(Set<Long> refundOrderIds, String operator) throws OrderBusinessException {
        List<RefundOrder> refundOrders = this.refundOrderRepository.findByIdInAndDelFlag(refundOrderIds, RefundOrder.DEL_FLAG_NO);
        for (RefundOrder refundOrder : refundOrders) {
            if (refundOrder.getStatus() != RefundOrder.STATUS_WAIT_SUBMIT) {
                throw new OrderBusinessException(OrderExceptionCodes.ORDER_STATUS_ERROR);
            }
        }
        refundOrders.forEach(refundOrder -> {
            refundOrder.setStatus(RefundOrder.STATUS_WAIT_AUDIT);
            refundOrder.setChangedAt(new Date());
            refundOrder.setChangedBy(operator);
            this.refundOrderRepository.update(refundOrder);
            this.saveRefundOrderLog(refundOrder.getId(), refundOrder.getRefundOrderSn(), RefundOrderLog.OPERATE_SUBMIT, "", operator);
        });
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void audit(Set<Long> refundOrderIds, String operator) throws OrderBusinessException {
        List<RefundOrder> refundOrders = this.refundOrderRepository.findByIdInAndDelFlag(refundOrderIds, RefundOrder.DEL_FLAG_NO);
        for (RefundOrder refundOrder : refundOrders) {
            if (refundOrder.getStatus() != RefundOrder.STATUS_WAIT_AUDIT) {
                throw new OrderBusinessException(OrderExceptionCodes.ORDER_STATUS_ERROR);
            }
        }
        refundOrders.forEach(refundOrder -> {
            refundOrder.setStatus(RefundOrder.STATUS_WAIT_PAY);
            refundOrder.setAuditTime(new Date());
            refundOrder.setChangedAt(new Date());
            refundOrder.setChangedBy(operator);
            this.refundOrderRepository.update(refundOrder);
            this.saveRefundOrderLog(refundOrder.getId(), refundOrder.getRefundOrderSn(), RefundOrderLog.OPERATE_AUDIT, "", operator);
        });
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void pay(Set<Long> refundOrderIds, String operator) throws OrderBusinessException {
        List<RefundOrder> refundOrders = this.refundOrderRepository.findByIdInAndDelFlag(refundOrderIds, RefundOrder.DEL_FLAG_NO);
        for (RefundOrder refundOrder : refundOrders) {
            if (refundOrder.getStatus() != RefundOrder.STATUS_WAIT_PAY) {
                throw new OrderBusinessException(OrderExceptionCodes.ORDER_STATUS_ERROR);
            }
        }
        Set<Long> orderIds = refundOrders.stream().map(RefundOrder::getOrderId).collect(Collectors.toSet());
        List<Order> orders = this.orderRepository.findByIdIn(orderIds);
        Map<Long, Integer> collect = orders.stream().collect(Collectors.toMap(Order::getId, Order::getRealPay));
        refundOrders.forEach(refundOrder -> {
            try {
                RefundRequestBO build = RefundRequestBO.builder()
                                                       .orderSn(refundOrder.getOrderSn())
                                                       .refundOrderSn(refundOrder.getRefundOrderSn())
                                                       .totalFee(collect.get(refundOrder.getOrderId()))
                                                       .notifyBusinessUrl(this.b2cHost + "/inner/notify/refund")
                                                       .refundFee(refundOrder.getAmount())
                                                       .build();
                log.info("请求支付系统退款 {}", build);
                OrderUtils.refund(build);
                this.saveRefundOrderLog(refundOrder.getId(), refundOrder.getRefundOrderSn(), RefundOrderLog.OPERATE_SUBMIT_PAY, "", operator);
            } catch (Exception e) {
                log.error("调用退款接口失败", e);
            }
        });

    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void invalid(Set<Long> refundOrderIds, String operator) {
        List<RefundOrder> refundOrders = this.refundOrderRepository.findByIdInAndDelFlag(refundOrderIds, RefundOrder.DEL_FLAG_NO);
        refundOrders.forEach(refundOrder -> {
            refundOrder.setStatus(RefundOrder.STATUS_INVALID);
            refundOrder.setChangedAt(new Date());
            refundOrder.setChangedBy(operator);
            this.refundOrderRepository.update(refundOrder);
            this.saveRefundOrderLog(refundOrder.getId(), refundOrder.getRefundOrderSn(), RefundOrderLog.OPERATE_PAY_INVALID, "", operator);
        });
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void notifyOrderRefundStatus(NotifyBusinessRefundBO request) throws BusinessException {
        String refundOrderSn = request.getRefundOrderSn();
        RefundOrder refundOrder = this.refundOrderRepository.findByRefundOrderSn(refundOrderSn);
        if (refundOrder == null) {
            throw new OrderBusinessException(OrderExceptionCodes.ORDER_NOT_FOUND);
        }
        Integer status = refundOrder.getStatus();
        if (status == RefundOrder.STATUS_COMPLETE) {
            log.warn("订单已经退款，不需要再次通知 {}", request);
            return;
        }

        if (status != RefundOrder.STATUS_WAIT_PAY) {
            throw new OrderBusinessException(OrderExceptionCodes.ORDER_STATUS_ERROR);
        }

        int refundOrderLogStatus = RefundOrderLog.OPERATE_PAY_FAIL;
        if (request.getRefundStatus() == NotifyBusinessRefundBO.REFUND_SUCCESS) {
            refundOrder.setStatus(RefundOrder.STATUS_COMPLETE);
            refundOrder.setChangedAt(new Date());
            refundOrder.setChangedBy("微信支付");
            this.refundOrderRepository.update(refundOrder);
            refundOrderLogStatus = RefundOrderLog.OPERATE_PAY_SUCCESS;
        }
        this.saveRefundOrderLog(refundOrder.getId(), refundOrder.getRefundOrderSn(), refundOrderLogStatus, request.getErrMsg(), "微信支付");
        List<OrderItem> orderItemList = orderItemRepository.findAllByOrderId(refundOrder.getOrderId());
        if (CollUtil.isEmpty(orderItemList)) {
            log.debug("退款消息发送失败，orderSn：{}", refundOrder.getOrderSn());
            return;
        }
        Long recomId = orderItemList.get(0).getRecomId();
        MedicationRecomBO medicationRecomBO = recommendFeignClient.getRecom(recomId);
        // 发送小程序订阅消息
        Map<String, Object> paramsMap = new HashMap<>(6);
        paramsMap.put("orderNumber", refundOrder.getOrderSn());
        paramsMap.put("productName", "处方订单");
        paramsMap.put("refundAmount", refundOrder.getAmount() / 100D);
        paramsMap.put("refundCause", "订单取消/退货");
        patientFeignClient.publishMaMessage(MsgMaPublishParamBO.builder()
                .doctorId(medicationRecomBO.getDoctorId())
                .patientId(medicationRecomBO.getPatientId())
                .msgType(MsgMaPublishParamBO.MsgType.REFUND_NOTICE)
                .content(JSONUtil.toJsonStr(paramsMap))
                .build());
    }

    /**
     * 保存退款日志
     */
    private void saveRefundOrderLog(Long refundOrderId, String refundOrderSn, int operate, String remark, String operator) {
        this.refundOrderLogRepository.save(RefundOrderLog.builder()
                                                         .refundOrderId(refundOrderId)
                                                         .refundOrderSn(refundOrderSn)
                                                         .operate(operate)
                                                         .remark(remark)
                                                         .createdAt(new Date())
                                                         .createdBy(operator)
                                                         .changedAt(new Date())
                                                         .changedBy(operator)
                                                         .build());
    }
}
